package com.eb.buybuybuy.core;

import com.eb.buybuybuy.annotation.Autowired;
import com.eb.buybuybuy.annotation.Component;
import com.eb.buybuybuy.annotation.Service;
import com.eb.buybuybuy.annotation.Transactional;
import com.eb.buybuybuy.constant.Configuration;
import com.eb.buybuybuy.context.TransactionContext;
import com.eb.buybuybuy.util.FileUtil;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.net.URL;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;

public class IOC {
    private static List<Class> classList = new ArrayList<>();
    private static Map<Class, Object> beanMap = new HashMap<>();
    private static boolean inited = false;

    public static void init() {
        if (inited) {
            return;
        }
        try {
            // 1 扫描指定包下所有文件
            scanFiles();

            // 2 扫描所有组件
            scanComponent();

            // 4 自动注入
            autowiredField();

            // 3 添加代理
            setExceptionProxy();

            inited = true;
        } catch (Exception e) {
            System.out.println("初始化失败");
            System.out.println(e.getMessage());
            throw new RuntimeException(e);
        }
    }

    /**
     * 扫描包下所有class文件
     *
     * @throws Exception
     */
    private static void scanFiles() throws Exception {
        ClassLoader classLoader = IOC.class.getClassLoader();

        URL rootUrl = classLoader.getResource(Configuration.PACKAGE_SCAN_ROOT);
        File rootFile = new File(rootUrl.getFile());

        List<File> files = FileUtil.getFiles(rootFile);

        // 加载所有class文件
        for (File file : files) {
            String classPath = FileUtil.getFileClassPath(Configuration.PACKAGE_SCAN_ROOT, file);
            Class clazz = Class.forName(classPath);
            classList.add(clazz);
        }
    }

    /**
     * 把所有被扫描的带有@Component注解的类加入容器
     *
     * @throws Exception
     */
    private static void scanComponent() throws Exception {
        for (Class clazz : classList) {
            Component componentAnnotation = (Component) clazz.getAnnotation(Component.class);
            Service serviceAnnotation = (Service) clazz.getAnnotation(Service.class);
            if (componentAnnotation != null) {
                if (serviceAnnotation != null) {
                    // 有Service注解，加入容器
                    Class clazzInterface = serviceAnnotation.proxyInterface();
                    beanMap.put(clazzInterface, clazz.newInstance());
                } else {
                    // 有Component注解，加入容器
                    beanMap.put(clazz, clazz.newInstance());
                }
            }
        }
    }

    /**
     * 设置事务代理
     */
    private static void setExceptionProxy() {
        for (Class clazz : beanMap.keySet()) {
            // 原始对象
            Object object = beanMap.get(clazz);

            if (object.getClass().getAnnotation(Service.class) != null) {
                // 获取原始对象中的所有方法
                List<Method> methodList = Arrays.stream(object.getClass().getDeclaredMethods()).filter((method -> {
                    return method.getAnnotation(Transactional.class) != null;
                })).collect(Collectors.toList());

                // 没有需要处理的方法 不代理
                if (methodList.isEmpty()) {
                    return;
                }

                Object proxy = Proxy.newProxyInstance(IOC.class.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException, SQLException {
                        boolean needTransactional = methodList.stream().anyMatch((m) -> {
                            return m.getName().equals(method.getName());
                        });

                        // 暂时允许访问
                        method.setAccessible(true);
                        Object methodReturn = null;
                        try {
                            if (needTransactional) {
                                // 增强方法
                                TransactionContext.start();
                                methodReturn = method.invoke(object, args);
                                TransactionContext.commit();
                            } else {
                                // 不需要增强
                                methodReturn = method.invoke(object, args);
                            }
                        } catch (ReflectiveOperationException | SQLException e) {
                            if (e.getCause() instanceof RuntimeException || e.getCause() instanceof SQLException) {
                                // 业务异常
                                if (needTransactional) {
                                    try {
                                        TransactionContext.rollback();
                                    } catch (SQLException ex) {
                                        System.out.println("自动回滚失败");
                                        throw new RuntimeException(ex);
                                    }
                                }
                                throw new RuntimeException(e.getCause().getMessage());
                            } else {
                                // 反射异常
                                throw new RuntimeException(e);
                            }
                        }

                        method.setAccessible(false);
                        return methodReturn;
                    }
                });
                beanMap.put(clazz, proxy);
            }
        }
    }

    /**
     * 自动注入
     *
     * @throws Exception
     */
    private static void autowiredField() throws Exception {
        for (Object object : beanMap.values()) {
            // 向对象的字段注入，不是key(key可能是接口的class)
            for (Field field : object.getClass().getDeclaredFields()) {
                Annotation annotation = field.getAnnotation(Autowired.class);
                if (annotation != null) {
                    // 带有Autowired注解 向field注入
                    Class requireType = field.getType();
                    // 向容器要这个类型的对象
                    Object obj = beanMap.get(requireType);
                    // 被设置的对象
                    Object setObj = object;

                    field.setAccessible(true);

                    field.set(setObj, obj);
                }
            }
        }
    }

    /**
     * 获取容器内组件
     *
     * @param clazz
     * @param <C>
     * @return
     */
    public static <C> C getComponent(Class<C> clazz) {
        return (C) beanMap.get(clazz);
    }

    public static Map<Class, Object> getBeanMap() {
        return beanMap;
    }
}
